home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Educational / Henon / Source / HenonView.m < prev    next >
Encoding:
Text File  |  1995-06-12  |  10.8 KB  |  484 lines

  1. //
  2. //    HenonView:View
  3. //
  4. //    written by Anders Bertelrud
  5. //    (c) 1990, 1991 by Anders Bertelrud
  6. //
  7.  
  8.  
  9. #import    "HenonView.h"
  10. #import    <dpsclient/dpsclient.h>
  11. #import    <appkit/appkit.h>
  12. #import    <objc/NXStringTable.h>
  13.  
  14.  
  15. #define        MAXREAL        1e+10
  16. //#define    MAXINT        65535
  17.  
  18.  
  19. @implementation HenonView
  20.  
  21.  
  22.  
  23. ////////////////////// Creation & Destruction Methods //////////////////////
  24.  
  25.  
  26. - initFrame:(const NXRect *)f
  27. {
  28.     self = [super initFrame:f];
  29.  
  30.     //  Create, initialize, and configure a new NXImage (for use as a
  31.     // mapping cache).
  32.     henonCache = [[NXImage alloc] initSize:&f->size];
  33.     [henonCache useCacheWithDepth:NX_TwoBitGrayDepth];
  34.  
  35.     useGrid = YES;
  36.     continuousPlot = YES;
  37.     isRunning = NO;
  38.  
  39.     timerTicking = NO;
  40.  
  41.     // Initialize the default plot values, and init the mapping cache.
  42.     [self setPlotValues1:self];
  43.     [self initHenonCache];
  44.  
  45.     return self;
  46. }
  47.  
  48.  
  49.  
  50. - free
  51. {
  52.     [self stopTimer];
  53.     [henonCache free];
  54.  
  55.     [super free];
  56.     return nil;
  57. }
  58.  
  59.  
  60.  
  61. //////////////////////////// Rendering Methods /////////////////////////////
  62.  
  63.  
  64. - drawSelf:(const NXRect *)rects :(int)rectCount
  65. {
  66.     //  Since the mapping is cached, all we have to do is to composite
  67.     // the cache into the panel.
  68.     [henonCache composite:NX_COPY toPoint:&bounds.origin];
  69.  
  70.     return self;
  71. }
  72.  
  73.  
  74.  
  75. /////////////////////////////// File Methods ///////////////////////////////
  76.  
  77.  
  78. - saveAsTIFF:sender
  79. {
  80.     id            savePanel = [SavePanel new];
  81.     NXStream    *tiffStream;
  82.  
  83.     //  This method should probably allow the user to specify compression
  84.     // type and (in the case of JPEG) quantization value. This could be
  85.     // done using an accessory view.
  86.     [savePanel setRequiredFileType:"tiff"];
  87.     if ([savePanel runModal])
  88.     {
  89.         if ((tiffStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL)
  90.         {
  91.             NXRunAlertPanel([stringTable valueForStringKey:"FS error"],
  92.                 [stringTable valueForStringKey:"Couldn't write TIFF to"],
  93.                 [stringTable valueForStringKey:"OK"], NULL, NULL,
  94.                 [savePanel filename]);
  95.         }
  96.         else
  97.         {
  98.             [henonCache writeTIFF:tiffStream];
  99.             NXSaveToFile(tiffStream, [savePanel filename]);
  100.             NXCloseMemory(tiffStream, NX_FREEBUFFER);
  101.         }
  102.     }
  103.  
  104.     return self;
  105. }
  106.  
  107.  
  108.  
  109. ////////////////////////////// Action Methods //////////////////////////////
  110.  
  111.  
  112. - startStop:sender
  113. {
  114.     if (!isRunning)
  115.     {
  116.         // We aren't already plotting a map, so start now.
  117.  
  118.         L = [[lField cell] floatValue];
  119.         T = [[tField cell] floatValue];
  120.         R = [[rField cell] floatValue];
  121.         B = [[bField cell] floatValue];
  122.  
  123.         //  Now check values and stuff. It is really very bad design philo-
  124.         // sophy to yell at the user using alert panels; this is just a cheap
  125.         // demo, however, so we'll leave it at this for now. At least we're
  126.         // using string tables to make internationalization easy.
  127.  
  128.         if (L >= R)
  129.         {
  130.             NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
  131.                 [stringTable valueForStringKey:"Right must be > left"],
  132.                 [stringTable valueForStringKey:"OK"], NULL, NULL);
  133.             return self;
  134.         }
  135.  
  136.         if (B >= T)
  137.         {
  138.             NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
  139.                 [stringTable valueForStringKey:"Top must be > bottom"],
  140.                 [stringTable valueForStringKey:"OK"], NULL, NULL);
  141.             return self;
  142.         }
  143.  
  144.         orbits = [[orbitField cell] intValue];
  145.         if (orbits < 1)
  146.         {
  147.             NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
  148.                 [stringTable valueForStringKey:"# orbits must be int >= 1"],
  149.                 [stringTable valueForStringKey:"OK"], NULL, NULL);
  150.             return self;
  151.         }
  152.  
  153.         points = [[pointsField cell] intValue];
  154.         if (points < 1)
  155.         {
  156.             NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
  157.                 [stringTable valueForStringKey:"# pts/orbit must be int >= 1"],
  158.                 [stringTable valueForStringKey:"OK"], NULL, NULL);
  159.             return self;
  160.         }
  161.  
  162.         dy0 = [[incYField cell] floatValue];
  163.         dx0 = [[incXField cell] floatValue];
  164.         y0 = [[startYField cell] floatValue];
  165.         x0 = [[startXField cell] floatValue];
  166.  
  167.         phase = [[phaseField cell] floatValue];
  168.         if (phase < 0 || phase > 3.141593)
  169.         {
  170.             NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
  171.                 [stringTable valueForStringKey:"Phase angle must be 0<=a<=pi"],
  172.                 [stringTable valueForStringKey:"OK"], NULL, NULL);
  173.             return self;
  174.         }
  175.  
  176.         // Calculate some things used by the caculation algorithm.
  177.         cosa = cos(phase);
  178.         sina = sin(phase);
  179.         xOld = x0;
  180.         yOld = y0;
  181.         xScale = bounds.size.width / (R - L);
  182.         yScale = bounds.size.height / (T - B);
  183.  
  184.         currentOrbit = 1;
  185.         currentPoint = 1;
  186.  
  187.         isRunning = YES;
  188.  
  189.         // Start the plot.
  190.         [[currentOrbitField cell] setIntValue:currentOrbit];
  191.         [self initHenonCache];
  192.         [self display];
  193.  
  194.         // Start the timer.
  195.         [self startTimer];
  196.  
  197.         [nextOrbitButton setEnabled:YES];
  198.         [startStopButton setTitle:[stringTable valueForStringKey:"Stop"]];
  199.     }
  200.     else
  201.     {
  202.         // We're plotting a map, so stop now that the button has been clicked.
  203.  
  204.         isRunning = NO;
  205.         [self stopTimer];
  206.         [nextOrbitButton setEnabled:NO];
  207.         [[currentOrbitField cell] setStringValue:""];
  208.         [startStopButton setTitle:[stringTable valueForStringKey:"Plot"]];
  209.     }
  210.     return self;
  211. }
  212.  
  213.  
  214.  
  215. - gridChecked:sender
  216. {
  217.     useGrid = [sender state];
  218.     return self;
  219. }
  220.  
  221.  
  222.  
  223. - nextOrbit:sender
  224. {
  225.     //  This is a quick way to make sure that we'll go into the next orbit
  226.     // the next time doHenonCalculation is called.
  227.     currentPoint = points + 1;
  228.     return self;
  229. }
  230.  
  231.  
  232.  
  233. - setPlotValues1:sender
  234. {
  235.     isRunning = YES;        // Hack so that startStop: will do the right thing.
  236.     [self startStop:self];    // Stop the current plot.
  237.  
  238.     phase = 1.111;        [phaseField setFloatValue:phase];
  239.                         [[phaseSlider cell] setFloatValue:phase];
  240.     L = -1.2;            [lField setFloatValue:L];
  241.     T = 1.2;            [tField setFloatValue:T];
  242.     R = 1.2;            [rField setFloatValue:R];
  243.     B = -1.2;            [bField setFloatValue:B];
  244.     x0 = 0.098;            [startXField setFloatValue:x0];
  245.     y0 = 0.061;            [startYField setFloatValue:y0];
  246.     dx0 = 0.04;            [incXField setFloatValue:dx0];
  247.     dy0 = 0.03;            [incYField setFloatValue:dy0];
  248.     orbits = 38;        [orbitField setIntValue:orbits];
  249.     points = 700;        [pointsField setIntValue:points];
  250.  
  251.     return self;
  252. }
  253.  
  254.  
  255.  
  256. - setPlotValues2:sender
  257. {
  258.     isRunning = YES;        // Hack so that startStop: will do the right thing.
  259.     [self startStop:self];    // Stop the current plot.
  260.  
  261.     phase = 0.264;        [phaseField setFloatValue:phase];
  262.                         [[phaseSlider cell] setFloatValue:phase];
  263.     L = -1.2;            [lField setFloatValue:L];
  264.     T = 1.2;            [tField setFloatValue:T];
  265.     R = 1.2;            [rField setFloatValue:R];
  266.     B = -1.2;            [bField setFloatValue:B];
  267.     x0 = 0.098;            [startXField setFloatValue:x0];
  268.     y0 = 0.061;            [startYField setFloatValue:y0];
  269.     dx0 = 0.04;            [incXField setFloatValue:dx0];
  270.     dy0 = 0.03;            [incYField setFloatValue:dy0];
  271.     orbits = 25;        [orbitField setIntValue:orbits];
  272.     points = 300;        [pointsField setIntValue:points];
  273.  
  274.     return self;
  275. }
  276.  
  277.  
  278.  
  279. - setPlotValues3:sender
  280. {
  281.     isRunning = YES;        // Hack so that startStop: will do the right thing.
  282.     [self startStop:self];    // Stop the current plot.
  283.  
  284.     phase = 1.5732;        [phaseField setFloatValue:phase];
  285.                         [[phaseSlider cell] setFloatValue:phase];
  286.     L = -3.0;            [lField setFloatValue:L];
  287.     T = 3.0;            [tField setFloatValue:T];
  288.     R = 3.0;            [rField setFloatValue:R];
  289.     B = -3.0;            [bField setFloatValue:B];
  290.     x0 = 0.098;            [startXField setFloatValue:x0];
  291.     y0 = 0.061;            [startYField setFloatValue:y0];
  292.     dx0 = 0.04;            [incXField setFloatValue:dx0];
  293.     dy0 = 0.03;            [incYField setFloatValue:dy0];
  294.     orbits = 70;        [orbitField setIntValue:orbits];
  295.     points = 400;        [pointsField setIntValue:points];
  296.  
  297.     return self;
  298. }
  299.  
  300.  
  301.  
  302. ////////////////////////////// Timer Methods ///////////////////////////////
  303.  
  304.  
  305. // The timed entry function.
  306. void    timedEntryFunction (DPSTimedEntry entryID, double currentTime, id self)
  307. {
  308.     [self doHenonCalculation];
  309. }
  310.  
  311.  
  312.  
  313. - startTimer
  314. {
  315.     [self stopTimer];
  316.     timerTicking = YES;
  317.     timer = DPSAddTimedEntry((double)0.0,
  318.         (DPSTimedEntryProc)&timedEntryFunction, self, NX_BASETHRESHOLD);
  319.     return self;
  320. }
  321.  
  322.  
  323.  
  324. - stopTimer
  325. {
  326.     
  327.     if (timerTicking)
  328.     {
  329.         DPSRemoveTimedEntry(timer);
  330.         timerTicking = NO;
  331.     }
  332.     return self;
  333. }
  334.  
  335.  
  336.  
  337. ////////////////////////// Miscellaneous Methods ///////////////////////////
  338.  
  339.  
  340. - doHenonCalculation
  341. {
  342.     float    xNew, yNew;
  343.  
  344.     //  If you want to modify the plot algorithm, this is the place to do it.
  345.     // This method is called continuously (from the timed entry function), and
  346.     // it calculates exactly one point in the Hénon map. The location of the
  347.     // point is based on the location of the previous point as well as on the
  348.     // values given in the plot panel.
  349.     //  If, after calculating the current point, we have calculated all the
  350.     // points in the orbit, we update the Hénon window and start the next
  351.     // orbit.
  352.  
  353.     if ([henonCache lockFocus])
  354.     {
  355.         PSsetgray(NX_BLACK);
  356.         if (currentPoint <= points)
  357.         {
  358.             if (abs(xOld) < MAXREAL && abs(yOld) < MAXREAL)
  359.             {
  360.                 xNew = xOld*cosa - (yOld - xOld*xOld)*sina;
  361.                 yNew = xOld*sina + (yOld - xOld*xOld)*cosa;
  362.  
  363.                 if (abs(xNew - L) < MAXINT/xScale
  364.                     && abs(T - yNew) < MAXINT/yScale)
  365.                 {
  366.                     float    x, y;
  367.  
  368.                     x = (xNew - L) * xScale;
  369.                     y = (T - yNew) * yScale;
  370.                     if (x >= bounds.origin.x
  371.                         && x <= bounds.origin.x + bounds.size.width
  372.                         && y >= bounds.origin.y
  373.                         && y <= bounds.origin.y + bounds.size.height)
  374.                     {
  375.                         PSmoveto(x, y);
  376.                         PSrlineto(0, 0);
  377.                         PSstroke();
  378.                     }
  379.                 }
  380.  
  381.                 xOld = xNew;
  382.                 yOld = yNew;
  383.             }
  384.         }
  385.         else
  386.         {
  387.             if (currentOrbit < orbits)
  388.             {
  389.                 // Calculate next starting point.
  390.                 xOld = x0 + currentOrbit * dx0;
  391.                 yOld = y0 + currentOrbit * dy0;
  392.  
  393.                 //  Reset the current point, and update the TextField in
  394.                 // the Hénon panel to reflect this change.
  395.                 currentPoint = 1;
  396.                 [[currentOrbitField cell] setIntValue:++currentOrbit];
  397.  
  398.                 // Update the panel to show the just completed orbit.
  399.                 [self lockFocus];
  400.                 [henonCache composite:NX_COPY toPoint:&bounds.origin];
  401.                 [self unlockFocus];
  402.                 [window flushWindow];
  403.             }
  404.             else
  405.             {
  406.                 // We're now finished with the plot, so clean things up.
  407.                 [self stopTimer];
  408.                 isRunning = NO;
  409.                 [startStopButton setTitle:"Plot"];
  410.                 [nextOrbitButton setEnabled:NO];
  411.                 [[currentOrbitField cell] setStringValue:""];
  412.             }
  413.         }
  414.  
  415.         //  Set things up so that the next point will be calculated the next
  416.         // time the timed entry calles doHenonCalculation.
  417.         ++currentPoint;
  418.  
  419.         [henonCache unlockFocus];
  420.     }
  421.  
  422.     return self;
  423. }
  424.  
  425.  
  426.  
  427. - initHenonCache
  428. {
  429.     float        bR, bT, bL, bB, bW, bH;
  430.     NXPoint    origin;
  431.  
  432.     [henonCache lockFocus];
  433.  
  434.     // Figure out shorthands for some edge values.
  435.     bL = bounds.origin.x;
  436.     bT = bounds.origin.y + bounds.size.height;
  437.     bR = bounds.origin.x + bounds.size.width;
  438.     bB = bounds.origin.y;
  439.     bW = bounds.size.width;
  440.     bH = bounds.size.height;
  441.  
  442.     PSsetgray(NX_WHITE);
  443.     NXRectFill(&bounds);
  444.  
  445.     origin.x = (0.0 - L) * bW / (R - L);
  446.     origin.y = (0.0 - B) * bH / (T - B);
  447.  
  448.     if ((R - L) < R && (T - B) < T)
  449.     {
  450.         origin.x = bR - 10;
  451.         origin.x = bB + 10;
  452.     }
  453.  
  454.     // If the user wants a grid, draw one.
  455.     if (useGrid)
  456.     {
  457.         PSmoveto(0, origin.y);
  458.         PSlineto(bR, origin.y);
  459.         PSmoveto(origin.x, 0);
  460.         PSlineto(origin.x, bT);
  461.  
  462.         PSsetgray(NX_DKGRAY);
  463.         PSsetlinewidth(0);
  464.         PSstroke();
  465.     }
  466.  
  467.     [henonCache unlockFocus];
  468.     return self;
  469. }
  470.  
  471.  
  472.  
  473. //////////////////////////// Delegation Methods ////////////////////////////
  474.  
  475.  
  476. - appDidInit:sender
  477. {
  478.     [window makeKeyAndOrderFront:self];
  479.     return self;
  480. }
  481.  
  482.  
  483.  
  484. @end